/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package br.ufrj.dcc.pushpeer.initialization;

import java.util.HashMap;
import java.util.Map;

import br.ufrj.dcc.pushpeer.dht.MD5;
import br.ufrj.dcc.pushpeer.dht.SimpleDHT;
import br.ufrj.dcc.pushpeer.protocol.PushPeer;
import peersim.config.Configuration;
import peersim.core.CommonState;
import peersim.core.Network;
import peersim.core.Node;

/**
 * This class provides a way to initialize a single node of the network.
 * The initialization is performed by choosing the bandwidth of the node
 * and choosing how much the shared files have been downloaded.
 */
public class NodeInitializer{
	
	/**
	 * The protocol to operate on.
	 */
	private static final String PROTOCOL = "protocol";
	
	/**
	 * The percentage of nodes with no downloaded pieces, for each file.
	 */
	private static final String NEW_NODES_DISTR="new_distr";
	
	/**
	 * The percentage of seeders on the network, for each file.
	 */
	private static final String SEEDER_NODES_DISTR="seeder_distr";
	
	/**
	 * The number of files being simulated.
	 */
	private static final String NUM_FILES="num_files";
	
	/**
	 * The average bandwidth of a node in Kbps.
	 */
	private static final String BANDWIDTH_AVG="bandwidth_avg";
	
	/**
	 * The standard deviation of the bandwidth of a node in Kbps.
	 */
	private static final String BANDWIDTH_STD_DEV="bandwidth_std_dev";
	
	/**
	 * The minimum bandwidth allowed in Kbps.
	 */
	private static final String BANDWIDTH_MIN="bandwidth_min";
	
	/**
	 * The average bandwidth of the nodes in Kbps.
	 */
	private double bandwidth_avg = 2969.6; //See: http://www.akamai.com/dl/akamai/akamai_soti_q412_exec_summary.pdf
	
	/**
	 * The standard deviation of the bandwidth of the nodes in Kbps.
	 */
	private double bandwidth_std_dev = 1024.0; //Assumed.
	
	/**
	 * The minimum bandwidth allowed in Kbps.
	 */
	private double bandwidth_min = 56.0; //Dial-up modem.
	
	/**
	 * The PushPeer protocol ID.
	 */	
	private final int pid;
	
	/**
	 * Number of files being simulated.
	 */
	private int numFiles;
	
	/**
	 * The percentage of nodes with no downloaded pieces (value), for each file (key).
	 */
	private Map<Integer, Integer> newDistr;
	
	/**
	 * The percentage of seeders on the network (value), for each file (key).
	 */
	private Map<Integer, Integer> seederDistr;

	/**
	 * The basic constructor of the class, which reads the parameters
	 * from the configuration file.
	 * @param prefix The configuration prefix for this class.
	 */
	public NodeInitializer(String prefix)
	{
		
		bandwidth_avg = Configuration.getDouble(prefix + "." + BANDWIDTH_AVG);
		
		bandwidth_std_dev = Configuration.getDouble(prefix + "." + BANDWIDTH_STD_DEV);
		
		bandwidth_min = Configuration.getDouble(prefix + "." + BANDWIDTH_MIN);
		
		pid = Configuration.getPid(prefix + "." + PROTOCOL);
		
		numFiles = Configuration.getInt(prefix + "." + NUM_FILES);
		
		newDistr = new HashMap<Integer, Integer>();
		for(int i=1; i <= numFiles; i++) {
			//The configuration file must contain the new nodes percentages for each individual file being simulated.
			newDistr.put(i, Configuration.getInt(prefix + "." + NEW_NODES_DISTR + "_" + i));
		}
		
		seederDistr = new HashMap<Integer, Integer>();
		for(int i=1; i <= numFiles; i++) {
			//The configuration file must contain the seeder percentages for each individual file being simulated.
			seederDistr.put(i, Configuration.getInt(prefix + "." + SEEDER_NODES_DISTR + "_" + i));
		}
		
	}
	
	/**
	 * Initializes the node <tt>n</tt> according to the PushPeer protocol definition.
	 * @param n The node to initialize.
	 * @see http://code.google.com/p/push-peer-protocol/wiki/PushPeerOverview
	 */
	public void initialize(Node n) {
		PushPeer p;
		p = (PushPeer)n.getProtocol(pid);
		p.setThisNodeID(n.getID());
		for(int i=1; i <= numFiles; i++)
		{
			/*
			 * Calculates whether or not a file is wanted based on the number of starting seeders and leechers.
			 */
			if(getWantedWithProbability(newDistr.get(i)))
				setFileStatus(i, p);
			else {
				p.setPeerStatus(i, PushPeer.NOT_INTERESTED);
				p.setFileWantedStatus(i, !PushPeer.WANTED);
				p.setFileNumPiecesCompleted(i, 0);
			}
		}
		
		setBandwidth(p);
		//Modulo Network.size() is used below to even out the probability distribution of the nodes being part of the result.
		p.setCB(SimpleDHT.findNodeByID(MD5.valueMD5(n.getID())%Network.size()));
		
		p.setInitialTime(CommonState.getTime());
		
		for(int i=1; i <= numFiles; i++) {
			p.alreadyPolled.put(i, false);
			p.downloadCompletionTime.put(i, Long.MIN_VALUE);
		}
	}
	
	/**
	 * Sets the status of the shared file according to the probability value given by {@link #getProbability()}.
	 * @param file The file being assigned.
	 * @param p The PushPeer protocol.
	 */
	private void setFileStatus(int file, PushPeer p) {
		int percentage = getProbability(seederDistr.get(file), newDistr.get(file));
		choosePieces(file, percentage, p);
	}
	
	/**
	 * Set the maximum bandwidth for the node, choosing from a Gaussian distribution with average {@link #BANDWIDTH_AVG}
	 * and standard deviation {@link #BANDWIDTH_STD_DEV}.
	 * @param p The PushPeer protocol.
	 */
	private void setBandwidth(PushPeer p) {
		
		double value;
		do {
			value = Math.abs(CommonState.r.nextGaussian())*bandwidth_std_dev + bandwidth_avg;
		} while(value <= bandwidth_min);

		p.setMaxBandwidth((int)Math.round(value));
	}
	
	/**
	 * Sets the completed pieces for the given protocol <tt>p</tt>.
	 * @param file The file being assigned.
	 * @param percentage The percentage of the downloaded pieces, according to {@link #getProbability()}.
	 * @param p The PushPeer protocol.
	 */
	private void choosePieces(int file, int percentage, PushPeer p) {
		double temp = ((double)p.getNPieces()/100.0)*percentage; // We use a double to avoid the loss of precision during the division operation.
		
		int completed = (int)temp; //integer number of pieces to set as completed.
		
		p.setFileNumPiecesCompleted(file, completed);
		if(percentage == 100) {
			p.setPeerStatus(file, PushPeer.SEEDER);
			p.setFileWantedStatus(file, !PushPeer.WANTED);
		}
		else {
			p.setPeerStatus(file, PushPeer.LEECHER);
			p.setFileWantedStatus(file, PushPeer.WANTED);
		}
		
		int tmp;
		while(completed != 0) { //Picks random pieces to set all it's blocks as downloaded.
			tmp = CommonState.r.nextInt(p.getNPieces());
			if(p.getFilePieceBlocksStatus(file, tmp) != PushPeer.NUM_BLOCKS) {
				p.setFilePieceBlocksStatus(file, tmp, PushPeer.NUM_BLOCKS);
				completed--;
			}
		}
		
	}
	
	/**
	 * Gets a probability according with the parameter <tt>new_distr</tt> defined in the configuration file.
	 * @param seederDistr The percentage of seeders on the network.
	 * @param newDistr The percentage of nodes with no download pieces.
	 * @return the probability value, where 0 means that the peer is new and no pieces has been downloaded,
	 * 100 means that the peer is a seeder; other values defines a random probability.
	 */
	private int getProbability(int seederDistr, int newDistr) {
		
		int value = CommonState.r.nextInt(101);
		if(value <= seederDistr)
			return 100;
		else
			return 0;
		/*value = CommonState.r.nextInt(101);
		if(value <= newDistr) {
			return 0; // A new peer, with probability newDistr
		}
		else {
			value = CommonState.r.nextInt(101);
			return value;
		}*/
	}
	
	/**
	 * Returns WANTED with probability p.
	 * @param p The probability.
	 * @return WANTED with probability p.
	 */
	private boolean getWantedWithProbability(int p) {
		
		int value = CommonState.r.nextInt(101);
		if(value <= p)
			return PushPeer.WANTED;
		else
			return !PushPeer.WANTED;
	}
	
}
